09 Generator与异步迭代
> Last Format Time:8/11/2026 21:45:39
本笔记由两个 JavaScript 教程目录中的同主题内容合并而成;全部旧知识保留在“旧笔记知识全集(按来源保留)”中,并由导航逐项索引。
当前前端开发关键要点
> [!important] 学习优先级:P2 > - 理解 Generator 同时是状态机和迭代器,可用于惰性序列、可暂停流程与自定义迭代。 > - 普通业务异步流程优先使用 async/await;Generator 更常见于库、框架或复杂迭代抽象。 > - 掌握 yield、next、throw、return 以及 for await...of 的控制关系。
知识点导航
[现代JS教程与阮一峰ES6 · 2函数/Generator函数](./09 Generator与异步迭代.md)
- generator是可迭代的
- generator是可以组合的
[现代JS教程与阮一峰ES6 · Generator&Iteration/Generator](./09 Generator与异步迭代.md)
- Generator
- generator 函数
- generator 是可迭代的
- 使用 generator 进行迭代
- generator 组合
- yield向内部传值
- generator.throw
- generator.return
- 总结
[现代JS教程与阮一峰ES6 · Generator&Iteration/异步迭代](./09 Generator与异步迭代.md)
- 异步迭代
[廖雪峰教程 · 4函数/4.8 生成器](./09 Generator与异步迭代.md)
- 生成器
现代前端补充与纠错
> [!info] 修改标记 > - 【修正】:旧教程中错误、过时或容易误导的内容。 > - 【补充】:旧教程未覆盖、但当前前端开发需要掌握的内容。 > - 【修正代码】:替换或校正了旧代码示例。
> [!warning] 下方保留历史示例;现代业务更常见的入口是异步迭代、流和可取消任务。
Generator 语义
- 【补充】 调用 Generator 函数只创建迭代器,不立即执行函数体;每次
next(value)从上次暂停处继续。 - 【修正】 第一次
next传入的值会被忽略,因为还没有暂停中的yield接收它。 - 【修正】
yield*委托给另一个可迭代对象;return产生最终值,但for...of不会把最终done: true的值作为元素遍历。 - 【修正】 应在
finally中释放资源,迭代器的return()可触发提前结束清理。
异步迭代
- 【补充】 异步数据源实现
Symbol.asyncIterator,通过for await...of消费;循环会等待每次迭代结果。 - 【补充】 Web Streams、分页接口和分块读取适合异步迭代,但仍需配合
AbortSignal、背压与错误传播。 - 【补充】 不要用 Generator 自建一套 Promise 调度器替代
async/await;手写调度器主要用于理解机制。
旧笔记知识全集(按来源保留)
现代JS教程与阮一峰ES6 · 2函数/Generator函数
ES6的异步编程解决方案。 形式上,Generator 函数是一个普通函数,但是有两个特征。一是,function关键字与函数名之间有一个星号;二是,函数体内部使用yield表达式,定义不同的内部状态(yield在英语里的意思就是“产出”)。
function* helloWorldGenerator() {
yield 'hello';
yield 'world';
return 'ending';
}
var hw = helloWorldGenerator();
上面代码定义了一个 Generator 函数helloWorldGenerator,它内部有两个yield表达式(hello和world),即该函数有三个状态:hello,world 和 return 语句(结束执行)。 调用 Generator 函数后,该函数并不执行,返回的也不是函数运行结果,而是一个指向内部状态的指针对象,也就是上一章介绍的遍历器对象(Iterator Object)。 下一步,必须调用遍历器对象的next方法,使得指针移向下一个状态。也就是说,每次调用next方法,内部指针就从函数头部或上一次停下来的地方开始执行,直到遇到下一个yield表达式(或return语句)为止。换言之,Generator 函数是分段执行的,yield表达式是暂停执行的标记,而next方法可以恢复执行。
hw.next()
// { value: 'hello', done: false }
hw.next()
// { value: 'world', done: false }
hw.next()
// { value: 'ending', done: true }
hw.next()
// { value: undefined, done: true }
const { log } = console;
function* fuck() {
log('fuck1')
yield log('fuck2')
yield log('fuck3')
yield log('fuck4')
return 'fuck you';
}
let fu = fuck();//不执行
fu;//不执行
fu.next(); //不会执行,直到调用next;打印fuck1和fuck2
fu.next()
fu.next()
fu.next()
fu.next()
fu.next();
遍历器对象的next方法的运行逻辑如下: (1)遇到yield表达式,就暂停执行后面的操作,并将紧跟在yield后面的那个表达式的值,作为返回的对象的value属性值。 (2)下一次调用next方法时,再继续往下执行,直到遇到下一个yield表达式。 (3)如果没有再遇到新的yield表达式,就一直运行到函数结束,直到return语句为止,并将return语句后面的表达式的值,作为返回的对象的value属性值。 (4)如果该函数没有return语句,则返回的对象的value属性值为undefined。 需要注意的是,yield表达式后面的表达式,只有当调用next方法、内部指针指向该语句时才会执行,因此等于为 JavaScript 提供了手动的“惰性求值”(Lazy Evaluation)的语法功能。
Generator 函数可以不用yield表达式,这时就变成了一个单纯的暂缓执行函数。
function* f() {
console.log('执行了!')
}
var generator = f();
setTimeout(function () {
generator.next()
}, 2000);
函数f如果是普通函数,在为变量generator赋值时就会执行。只有调用next方法时,Generator函数f才会执行。 另外需要注意,yield表达式只能用在 Generator 函数里面,用在其他地方都会报错。 yield表达式如果用在另一个表达式之中,必须放在圆括号里面。
function* demo() {
console.log('Hello' + yield); // SyntaxError
console.log('Hello' + yield 123); // SyntaxError
console.log('Hello' + (yield)); // OK
console.log('Hello' + (yield 123)); // OK
}
yield表达式用作函数参数或放在赋值表达式的右边,可以不加括号。
function* demo() {
foo(yield 'a', yield 'b'); // OK
let input = yield; // OK
}
generator是可迭代的
function* generateSequence() {
yield 1;
yield 2;
return 3;
}
let generator = generateSequence();
for(let value of generator) {
alert(value); // 1,然后是 2
}
上面这个例子会先显示 1,然后是 2,然后就没了。它不会显示 3!
这是因为当 done: true 时,for..of 循环会忽略最后一个 value。因此,如果我们想要通过 for..of 循环显示所有的结果,我们必须使用 yield 返回它们
因为 generator 是可迭代的,我们可以使用 iterator 的所有相关功能,例如:spread 语法 ...:
function* generateSequence() {
yield 1;
yield 2;
yield 3;
}
let sequence = [0, ...generateSequence()];
alert(sequence); // 0, 1, 2, 3
generator是可以组合的
我们可以在其内部使用yield来组合他们
function* generateSequence(start, end) {
for (let i = start; i <= end; i++) yield i;
}
function* generatePasswordCodes() {
// 0..9
yield* generateSequence(48, 57);
// A..Z
yield* generateSequence(65, 90);
// a..z
yield* generateSequence(97, 122);
}
let str = '';
for(let code of generatePasswordCodes()) {
str += String.fromCharCode(code);
}
alert(str); // 0..9A..Za..z
yield 是一条双向路(two-way street):它不仅可以向外返回结果,而且还可以将外部的值传递到 generator 内。
调用 generator.next(arg),我们就能将参数 arg 传递到 generator 内部。这个 arg 参数会变成 yield 的结果:
function* gen() {
let ask1 = yield "2 + 2 = ?";
alert(ask1); // 4
let ask2 = yield "3 * 3 = ?"
alert(ask2); // 9
}
let generator = gen();
alert( generator.next().value ); // "2 + 2 = ?"
alert( generator.next(4).value ); // "3 * 3 = ?"
alert( generator.next(9).done ); // true
要向 yield 传递一个 error,我们应该调用 generator.throw(err)。在这种情况下,err 将被抛到对应的 yield 所在的那一行。
generator.return(value) 完成 generator 的执行并返回给定的 value。
现代JS教程与阮一峰ES6 · Generator&Iteration/Generator
Generator
https://zh.javascript.info/generators
generator 函数
要创建一个 generator,我们需要一个特殊的语法结构:function*,即所谓的 “generator function”。
它看起来像这样:
function* generateSequence() {
yield 1;
yield 2;
return 3;
}
generator 函数与常规函数的行为不同。在此类函数被调用时,它不会运行其代码。而是返回一个被称为 “generator object” 的特殊对象,来管理执行流程:
function* generateSequence() {
yield 1;
yield 2;
return 3;
}
// "generator function" 创建了一个 "generator object"
let generator = generateSequence();
alert(generator); // [object Generator]
到目前为止,上面这段代码中的 函数体 代码还没有开始执行:
一个 generator 的主要方法就是 next()。当被调用时(译注:指 next() 方法),它会恢复上图所示的运行,执行直到最近的 yield <value> 语句(value 可以被省略,默认为 undefined)。然后函数执行暂停,并将产出的(yielded)值返回到外部代码。
next() 的结果始终是一个具有两个属性的对象:
value: 产出的(yielded)的值。done: 如果 generator 函数已执行完成则为true,否则为false。
例如,我们可以创建一个 generator 并获取其第一个产出的(yielded)值:
function* generateSequence() {
yield 1;
yield 2;
return 3;
}
let generator = generateSequence();
let one = generator.next();
alert(JSON.stringify(one)); // {value: 1, done: false}
让我们再次调用 generator.next()。代码恢复执行并返回下一个 yield 的值:
let two = generator.next();
alert(JSON.stringify(two)); // {value: 2, done: false}
如果我们第三次调用 generator.next(),代码将会执行到 return 语句,此时就完成这个函数的执行:
let three = generator.next();
alert(JSON.stringify(three)); // {value: 3, done: true}
现在 generator 执行完成。我们通过 done:true 可以看出来这一点,并且将 value:3 处理为最终结果。
再对 generator.next() 进行新的调用不再有任何意义。如果我们这样做,它将返回相同的对象:{done: true}。
function* f(…) 或 function *f(…)?这两种语法都是对的。但是通常更倾向于第一种语法,因为星号 * 表示它是一个 generator 函数,它描述的是函数种类而不是名称,因此 * 应该和 function 关键字紧贴一起。
generator 是可迭代的
当你看到 next() 方法,或许你已经猜到了 generator 是 可迭代(iterable)的。
我们可以使用 for..of 循环遍历它所有的值:
function* generateSequence() {
yield 1;
yield 2;
return 3;
}
let generator = generateSequence();
for(let value of generator) {
alert(value); // 1,然后是 2
}
for..of 写法是不是看起来比 .next().value 优雅多了?
……但是请注意:上面这个例子会先显示 1,然后是 2,然后就没了。它不会显示 3!
这是因为当 done: true 时,for..of 循环会忽略最后一个 value。因此,如果我们想要通过 for..of 循环显示所有的结果,我们必须使用 yield 返回它们:
function* generateSequence() {
yield 1;
yield 2;
yield 3;
}
let generator = generateSequence();
for(let value of generator) {
alert(value); // 1,然后是 2,然后是 3
}
因为 generator 是可迭代的,可以使用 iterator 的所有相关功能,spread 语法 ...:
function* generateSequence() {
yield 1;
yield 2;
yield 3;
}
let sequence = [0, ...generateSequence()];
alert(sequence); // 0, 1, 2, 3
在上面这段代码中,...generateSequence() 将可迭代的 generator 对象转换为了一个数组(关于 spread 语法的更多细节请见 Rest 参数与 Spread 语法)。
使用 generator 进行迭代
在前面的 Iterable object(可迭代对象) 一章中,我们创建了一个可迭代的 range 对象,它返回 from..to 的值:
let range = {
from: 1,
to: 5,
// for..of range 在一开始就调用一次这个方法
[Symbol.iterator]() {
// ...它返回 iterator object:
// 后续的操作中,for..of 将只针对这个对象,并使用 next() 向它请求下一个值
return {
current: this.from,
last: this.to,
// for..of 循环在每次迭代时都会调用 next()
next() {
// 它应该以对象 {done:.., value :...} 的形式返回值
if (this.current <= this.last) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
}
};
// 迭代整个 range 对象,返回从 `range.from` 到 `range.to` 范围的所有数字
alert([...range]); // 1,2,3,4,5
我们可以通过提供一个 generator 函数作为 Symbol.iterator,来使用 generator 进行迭代:
let range = {
from: 1,
to: 5,
*[Symbol.iterator]() { // [Symbol.iterator]: function*() 的简写形式
for(let value = this.from; value <= this.to; value++) {
yield value;
}
}
};
alert( [...range] ); // 1,2,3,4,5
之所以代码正常工作,是因为 range[Symbol.iterator]() 现在返回一个 generator,而 generator 方法正是 for..of 所期望的:
- 它具有
.next()方法 - 它以
{value: ..., done: true/false}的形式返回值
当然,这不是巧合。generator 被添加到 JavaScript 语言中是有对 iterator 的考量的,以便更容易地实现 iterator。
带有 generator 的变体比原来的 range 迭代代码简洁得多,并且保持了相同的功能。
generator 可以永远产出(yield)值
在上面的示例中,我们生成了有限序列,但是我们也可以创建一个生成无限序列的 generator,它可以一直产出(yield)值。例如,无序的伪随机数序列。
这种情况下肯定需要在 generator 的 for..of 循环中添加一个 break(或者 return)。否则循环将永远重复下去并挂起。
generator 组合
generator 组合(composition)是 generator 的一个特殊功能,它允许透明地(transparently)将 generator 彼此“嵌入(embed)”到一起。
例如,我们有一个生成数字序列的函数:
function* generateSequence(start, end) {
for (let i = start; i <= end; i++) yield i;
}
现在,我们想重用它来生成一个更复杂的序列:
- 首先是数字
0..9(字符代码为 48…57), - 接下来是大写字母
A..Z(字符代码为 65…90) - 接下来是小写字母
a...z(字符代码为 97…122)
我们可以对这个序列进行应用,例如,我们可以从这个序列中选择字符来创建密码(也可以添加语法字符),但让我们先生成它。
在常规函数中,要合并其他多个函数的结果,我们需要调用它们,存储它们的结果,最后再将它们合并到一起。
对于 generator 而言,我们可以使用 yield* 这个特殊的语法来将一个 generator “嵌入”(组合)到另一个 generator 中:
组合的 generator 的例子:
function* generateSequence(start, end) {
for (let i = start; i <= end; i++) yield i;
}
function* generatePasswordCodes() {
// 0..9
yield* generateSequence(48, 57);
// A..Z
yield* generateSequence(65, 90);
// a..z
yield* generateSequence(97, 122);
}
let str = '';
for(let code of generatePasswordCodes()) {
str += String.fromCharCode(code);
}
alert(str); // 0..9A..Za..z
yield* 指令将执行 委托 给另一个 generator。这个术语意味着 yield* gen 在 generator gen 上进行迭代,并将其产出(yield)的值透明地(transparently)转发到外部。就好像这些值就是由外部的 generator yield 的一样。
执行结果与我们内联嵌套 generator 中的代码获得的结果相同:
function* generateSequence(start, end) {
for (let i = start; i <= end; i++) yield i;
}
function* generateAlphaNum() {
// yield* generateSequence(48, 57);
for (let i = 48; i <= 57; i++) yield i;
// yield* generateSequence(65, 90);
for (let i = 65; i <= 90; i++) yield i;
// yield* generateSequence(97, 122);
for (let i = 97; i <= 122; i++) yield i;
}
let str = '';
for(let code of generateAlphaNum()) {
str += String.fromCharCode(code);
}
alert(str); // 0..9A..Za..z
generator 组合(composition)是将一个 generator 流插入到另一个 generator 流的自然的方式。它不需要使用额外的内存来存储中间结果。
yield向内部传值
目前看来,generator 和可迭代对象类似,都具有用来生成值的特殊语法。但实际上,generator 更加强大且灵活。
这是因为 yield 是一条双向路(two-way street):它不仅可以向外返回结果,而且还可以将外部的值传递到 generator 内。
调用 generator.next(arg),我们就能将参数 arg 传递到 generator 内部。这个 arg 参数会变成 yield 的结果:
function* gen() {
// 向外部代码传递一个问题并等待答案
let result = yield "2 + 2 = ?"; // (*)
alert(result);
}
let generator = gen();
let question = generator.next().value; // <-- yield 返回的 value
generator.next(4); // --> 将结果传递到 generator 中
- 第一次调用
generator.next()应该是不带参数的(如果带参数,那么该参数会被忽略)。它开始执行并返回第一个yield "2 + 2 = ?"的结果。此时,generator 执行暂停,而停留在(*)行上。 - 然后,正如上面图片中显示的那样,
yield的结果进入调用代码中的question变量。 - 在
generator.next(4),generator 恢复执行,并获得了4作为结果:let result = 4。
请注意,外部代码不必立即调用 next(4)。外部代码可能需要一些时间。这没问题:generator 将等待它。
例如:
// 一段时间后恢复 generator
setTimeout(() => generator.next(4), 1000);
我们可以看到,与常规函数不同,generator 和调用 generator 的代码可以通过在 next/yield 中传递值来交换结果。
为了讲得更浅显易懂,我们来看另一个例子,其中包含了许多调用:
function* gen() {
let ask1 = yield "2 + 2 = ?";
alert(ask1); // 4
let ask2 = yield "3 * 3 = ?"
alert(ask2); // 9
}
let generator = gen();
alert( generator.next().value ); // "2 + 2 = ?"
alert( generator.next(4).value ); // "3 * 3 = ?"
alert( generator.next(9).done ); // true
执行图:
- 第一个
.next()启动了 generator 的执行……执行到达第一个yield。 - 结果被返回到外部代码中。
- 第二个
.next(4)将4作为第一个yield的结果传递回 generator 并恢复 generator 的执行。 - ……执行到达第二个
yield,它变成了 generator 调用的结果。 - 第三个
next(9)将9作为第二个yield的结果传入 generator 并恢复 generator 的执行,执行现在到达了函数的最底部,所以返回done: true。
这个过程就像“乒乓球”游戏。每个 next(value)(除了第一个)传递一个值到 generator 中,该值变成了当前 yield 的结果,然后获取下一个 yield 的结果。
generator.throw
正如我们在上面的例子中观察到的那样,外部代码可能会将一个值传递到 generator,作为 yield 的结果。
……但是它也可以在那里发起(抛出)一个 error。这很自然,因为 error 本身也是一种结果。
要向 yield 传递一个 error,我们应该调用 generator.throw(err)。在这种情况下,err 将被抛到对应的 yield 所在的那一行。
例如,"2 + 2?" 的 yield 导致了一个 error:
function* gen() {
try {
let result = yield "2 + 2 = ?"; // (1)
alert("The execution does not reach here, because the exception is thrown above");
} catch(e) {
alert(e); // 显示这个 error
}
}
let generator = gen();
let question = generator.next().value;
generator.throw(new Error("The answer is not found in my database")); // (2)
在 (2) 行引入到 generator 的 error 导致了在 (1) 行中的 yield 出现了一个异常。在上面这个例子中,try..catch 捕获并显示了这个 error。
如果我们没有捕获它,那么就会像其他的异常一样,它将从 generator “掉出”到调用代码中。
调用代码的当前行是 generator.throw 所在的那一行,标记为 (2)。所以我们可以在这里捕获它,就像这样:
function* generate() {
let result = yield "2 + 2 = ?"; // 这行出现 error
}
let generator = generate();
let question = generator.next().value;
try {
generator.throw(new Error("The answer is not found in my database"));
} catch(e) {
alert(e); // 显示这个 error
}
如果我们没有在那里捕获这个 error,那么,通常,它会掉入外部调用代码(如果有),如果在外部也没有被捕获,则会杀死脚本。
generator.return
generator.return(value) 完成 generator 的执行并返回给定的 value:
function* gen() {
yield 1;
yield 2;
yield 3;
}
const g = gen();
g.next(); // { value: 1, done: false }
g.return('foo'); // { value: "foo", done: true }
g.next(); // { value: undefined, done: true }
如果我们在已完成的 generator 上再次使用 generator.return(),它将再次返回该值(MDN)。
通常我们不使用它,因为大多数时候我们想要获取所有的返回值,但是当我们想要在特定条件下停止 generator 时它会很有用。
总结
- generator 是通过 generator 函数
function* f(…) {…}创建的。 - 在 generator(仅在)内部,存在
yield操作。 - 外部代码和 generator 可能会通过
next/yield调用交换结果。
在现代 JavaScript 中,generator 很少被使用。但有时它们会派上用场,因为函数在执行过程中与调用代码交换数据的能力是非常独特的。而且,当然,它们非常适合创建可迭代对象。
在 Web 编程中,我们经常使用数据流,因此这是另一个非常重要的使用场景。
现代JS教程与阮一峰ES6 · Generator&Iteration/异步迭代
异步迭代
https://zh.javascript.info/async-iterators-generators
廖雪峰教程 · 4函数/4.8 生成器
生成器
生成器(generator)是ES6标准引入的新的数据类型。一个生成器看上去像一个函数,但可以返回多次。
ES6定义生成器标准的哥们借鉴了Python的generator的概念和语法,如果你对Python的generator很熟悉,那么ES6的generator就是小菜一碟了。如果你对Python还不熟,赶快恶补Python教程!。
我们先复习函数的概念。一个函数是一段完整的代码,调用一个函数就是传入参数,然后返回结果:
function foo(x) {
return x + x;
}
let r = foo(1); // 调用foo函数
函数在执行过程中,如果没有遇到return语句(函数末尾如果没有return,就是隐含的return undefined;),控制权无法交回被调用的代码。
generator跟函数很像,定义如下:
function* foo(x) {
yield x + 1;
yield x + 2;
return x + 3;
}
generator和函数不同的是,generator由function*定义(注意多出的*号),并且,除了return语句,还可以用yield返回多次。
大多数同学立刻就晕了,generator就是能够返回多次的“函数”?返回多次有啥用?
还是举个栗子吧。
我们以一个著名的斐波那契数列为例,它由0,1开头:
0 1 1 2 3 5 8 13 21 34 ...
要编写一个产生斐波那契数列的函数,可以这么写:
function fib(max) {
let
t,
a = 0,
b = 1,
arr = [0, 1];
while (arr.length < max) {
[a, b] = [b, a + b];
arr.push(b);
}
return arr;
}
// 测试:
fib(5); // [0, 1, 1, 2, 3]
fib(10); // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]
函数只能返回一次,所以必须返回一个Array。但是,如果换成generator,就可以一次返回一个数,不断返回多次。用generator改写如下:
function* fib(max) {
let
t,
a = 0,
b = 1,
n = 0;
while (n < max) {
yield a;
[a, b] = [b, a + b];
n ++;
}
return;
}
直接调用试试:
fib(5); // fib {GeneratorStatus: "suspended", GeneratorReceiver: Window}
直接调用一个generator和调用函数不一样,fib(5)仅仅是创建了一个generator对象,还没有去执行它。
调用generator对象有两个方法,一是不断地调用generator对象的next()方法:
let f = fib(5);
f.next(); // {value: 0, done: false}
f.next(); // {value: 1, done: false}
f.next(); // {value: 1, done: false}
f.next(); // {value: 2, done: false}
f.next(); // {value: 3, done: false}
f.next(); // {value: undefined, done: true}
next()方法会执行generator的代码,然后,每次遇到yield x;就返回一个对象{value: x, done: true/false},然后“暂停”。返回的value就是yield的返回值,done表示这个generator是否已经执行结束了。如果done为true,则value就是return的返回值。
当执行到done为true时,这个generator对象就已经全部执行完毕,不要再继续调用next()了。
第二个方法是直接用for ... of循环迭代generator对象,这种方式不需要我们自己判断
generator和普通函数相比,有什么用?
因为generator可以在执行过程中多次返回,所以它看上去就像一个可以记住执行状态的函数,利用这一点,写一个generator就可以实现需要用面向对象才能实现的功能。例如,用一个对象来保存状态,得这么写:
let fib = {
a: 0,
b: 1,
n: 0,
max: 5,
next: function () {
let
r = this.a,
t = this.a + this.b;
this.a = this.b;
this.b = t;
if (this.n < this.max) {
this.n ++;
return r;
} else {
return undefined;
}
}
};
用对象的属性来保存状态,相当繁琐。
generator还有另一个巨大的好处,就是把异步回调代码变成“同步”代码。这个好处要等到后面学了AJAX以后才能体会到。
没有generator之前的黑暗时代,用AJAX时需要这么写代码:
ajax('http://url-1', data1, function (err, result) {
if (err) {
return handle(err);
}
ajax('http://url-2', data2, function (err, result) {
if (err) {
return handle(err);
}
ajax('http://url-3', data3, function (err, result) {
if (err) {
return handle(err);
}
return success(result);
});
});
});
回调越多,代码越难看。
有了generator的美好时代,用AJAX时可以这么写:
try {
r1 = yield ajax('http://url-1', data1);
r2 = yield ajax('http://url-2', data2);
r3 = yield ajax('http://url-3', data3);
success(r3);
}
catch (err) {
handle(err);
}
看上去是同步的代码,实际执行是异步的。